#!/usr/bin/env python

import sys
import os.path
import os
import mimetypes;
import thumbnails
import catalogs
import importer;
import exiftool;
import xmp;
import pygtk;
import gtk
import gtk.glade
import gobject
import pango;
import gobject;
import events;
    
supportedTypes = gtk.gdk.pixbuf_get_formats();

def createPropertyEditor(xmpType,lang,label):
    
    if xmpType.name == 'Lang Alt':
        return LangAltPropertyEditor(xmpType,lang,label);
    if xmpType.isAlternative():
        return ArrayPropertyEditor(xmpType,lang,label);
    elif xmpType.isArray():
        return ArrayPropertyEditor(xmpType,lang,label);
    elif xmpType.isStructure():
        return None;
    elif isinstance(xmpType,xmp.Date):
        return DatePropertyEditor(xmpType,lang,label);
    return PropertyEditor(xmpType,lang,label);

class PropertyEditor(events.EventSource):
    
    def __init__(self,xmpType,lang,label):
        events.EventSource.__init__(self);
        self.lang = lang;
        self.__dirty = False;
        self.oldValue = None;
        self.xmpType = xmpType;
        self.createLabelWidget(label);
        self.createEditWidget();
        self.createDirtyWidget();
        self.catalog = None;
        self.changingValue = False;
        self.multi = False;
        
    def createLabelWidget(self,label):
        if label == None:
            self.labelWidget = None;
        else:
            self.labelWidget = gtk.Label(label+':');
            self.labelWidget.set_alignment(0, 0.5)
    
    def createEditWidget(self):
        self.choicesModel = gtk.ListStore(str,str);
        self.choicesModel.set_sort_column_id(0,gtk.SORT_ASCENDING);
        if self.xmpType.choiceType == xmp.CLOSED_CHOICE:
            self.editWidget = gtk.ComboBox(self.choicesModel);
            cell = gtk.CellRendererText();
            self.editWidget.pack_start(cell, False);
            self.editWidget.add_attribute(cell, 'text', 0);
            cell = gtk.CellRendererText();
            self.editWidget.pack_start(cell, True);
            self.editWidget.add_attribute(cell, 'text', 1);
        else:
            self.editWidget = gtk.Entry();
            self.editWidget.connect('changed',self.onEditWidgetDirty);
            self.editWidget.connect('activate',self.onActivate);
            self.editWidget.connect('focus-out-event',self.onFocusOut);
            self.editWidget.connect_after('focus-in-event',self.onFocusIn);
            self.editWidget.connect("key-press-event",self.onKeyPress);
            self.completion = gtk.EntryCompletion();
            self.editWidget.set_completion(self.completion);
            self.completion.set_model(self.choicesModel);
            self.completion.set_text_column(0);
            cell = gtk.CellRendererText();
            self.completion.pack_start(cell, True);
            self.completion.add_attribute(cell, 'text', 1);
            self.completion.set_minimum_key_length(0);
            self.bgColors = ( self.editWidget.get_style().base[gtk.STATE_NORMAL],self.editWidget.get_style().base[gtk.STATE_INSENSITIVE] );
            self.textColors = ( self.editWidget.get_style().text[gtk.STATE_NORMAL],self.editWidget.get_style().text[gtk.STATE_INSENSITIVE] );
        self.editBox = self.editWidget;
            
    def createDirtyWidget(self):
        self.dirtyWidget = gtk.CheckButton();
        self.dirtyWidget.unset_flags(gtk.CAN_FOCUS);
        self.dirtyWidget.connect('toggled',self.onDirtyWidgetToggled)
    
    def attachWidget(self,grid,widget,x,y,xoptions,yoptions):
        if widget != None:
            if widget.parent == None:
                grid.attach(widget,x,x+1,y,y+1,xoptions,yoptions,0,0);
                widget.show_all();
            else:
                grid.child_set_property(widget,'top-attach',y);
                grid.child_set_property(widget,'bottom-attach',y+1);
    
    def setValue(self,value):
        self.setValues(value,());
        
    def setValues(self,value,values):
        self.oldValue = value;
        self.__setValue__(value);
        self.setDirty(False);
        multi = False;
        for v in values:
            if v != value:
                multi = True;
                break;
        self.setMulti(multi);
        
    def __setValue__(self,value):
        self.changingValue = True;
        try:
            if self.xmpType.choiceType == xmp.CLOSED_CHOICE:
                pass;
            else:
                self.editWidget.set_text(self.format(value));
        finally:
            self.changingValue = False;
        
    def setSensitive(self,sensitive):
        if self.editWidget != None:
            self.editWidget.set_sensitive(sensitive);
        if self.labelWidget != None:
            self.labelWidget.set_sensitive(sensitive);
        if self.dirtyWidget != None:
            self.dirtyWidget.set_sensitive(sensitive);
    
    def configure(self,grid,position):
        self.attachWidget(grid,self.dirtyWidget,2,position,gtk.FILL, gtk.FILL);
        self.attachWidget(grid,self.editBox,3,position,gtk.EXPAND|gtk.FILL, gtk.FILL);
        self.attachWidget(grid,self.labelWidget,1,position,gtk.FILL,gtk.FILL);
        return position+1;
    
    def getValue(self):
        if self.xmpType.choiceType == xmp.CLOSED_CHOICE:
            return None;
        else:
            return self.parse(self.editWidget.get_text());
        
    def parse(self,text):
        return self.xmpType.parse(text);
    
    def format(self,value):
        text = self.xmpType.format(value);
        if text == None:
            return '';
        return text;
    
    def updateDirtyStyle(self):
        if self.dirtyWidget != None:
            self.dirtyWidget.set_active(self.isDirty());
            
    def setDirty(self,dirty):
        self.__dirty = dirty;
        self.updateDirtyStyle();
        self.emit('dirty',dirty);
        
    def isDirty(self):
        return self.__dirty;
    
    def onEditWidgetDirty(self,widget):
        if not self.changingValue:
            self.setDirty(True);
        
    def onDirtyWidgetToggled(self,widget):
        self.setDirty(widget.get_active());
        if not self.isDirty():
            self.__setValue__(self.oldValue);

    def onActivate(self,widget):
        self.emit('accept');
        
    def onFocusOut(self,widget,event):
        self.emit('accept');
        
    def onFocusIn(self,widget,event):
        self.editWidget.select_region(0,len(self.editWidget.get_text())-1);
        
    def setCatalog(self,catalog):
        self.catalog = catalog;
        self.updateChoices();
            
    def onKeyPress(self,widget,event):
        if gtk.gdk.keyval_name(event.keyval) == "Escape":
            self.setDirty(False);
            if not self.isDirty():
                self.__setValue__(self.oldValue);
            return True
        return False
        
    def updateChoices(self):
        self.choicesModel.clear();
        if self.catalog != None:
            for element in self.catalog.getDictionary(self.xmpType.path).elements.itervalues():
                label = element.getLabel(self.lang);
                if label == None:
                    label = element.getLabel(xmp.DEFAULT_LANG);
                self.choicesModel.append((self.xmpType.format(element.value),label));
            self.choicesModel.append(('value','label'));
                
    def dictionaryChanged(self,dictionary):
        self.updateChoices();
        
    def focus(self):
        self.editWidget.grab_focus();
        
    def updateCatalog(self,catalog,file,property,path):
        if isDirty():
            catalog.setTag(file,property,path,getValue());
            
    def setRemoved(self,removed):
        self.editWidget.set_sensitive(not removed);
        if self.xmpType.choiceType == xmp.CLOSED_CHOICE:
            pass;
        else:
            l = pango.AttrList()
            l.insert(pango.AttrStrikethrough(removed,0,len(self.editWidget.get_text())))
            self.editWidget.get_layout().set_attributes(l);
            
    def isEmpty(self):
        if self.xmpType.choiceType == xmp.CLOSED_CHOICE:
            pass;
        else:
            return len(self.editWidget.get_text()) == 0;
        
    def setMulti(self,multi):
        self.multi = multi;
        if self.xmpType.choiceType == xmp.CLOSED_CHOICE:
            pass;
        else:
            if multi:
                self.editWidget.modify_text(gtk.STATE_NORMAL,self.textColors[1]);
                self.editWidget.modify_base(gtk.STATE_NORMAL,self.bgColors[1]);
            else:
                self.editWidget.modify_text(gtk.STATE_NORMAL,self.textColors[0]);
                self.editWidget.modify_base(gtk.STATE_NORMAL,self.bgColors[0]);
        
    def isMulti(self):
        return self.multi;
    
class DatePropertyEditor(PropertyEditor):
    
    def __init__(self,xmpType,lang,label):
        PropertyEditor.__init__(self, xmpType,lang, label);

    def createEditWidget(self):
        PropertyEditor.createEditWidget(self);
        self.editBox = self.box = gtk.HBox();
        self.box.pack_start(self.editWidget);
        
        self.calendarButton = gtk.Button('...');
        self.calendarButton.unset_flags(gtk.CAN_FOCUS);
        self.calendarButton.connect('clicked',self.onCalendarButton);
        self.box.pack_end(self.calendarButton,False,False);
        
        tree = gtk.glade.XML(os.path.join(os.path.dirname(__file__),"calendar.glade"))
        self.calendarWindow = tree.get_widget("calendarPopup")
        tree.signal_autoconnect(self)
        
    def onCalendarButton(self,widget):
        rect = widget.get_allocation();
        x, y = widget.window.get_origin();
        cal_width, cal_height = self.calendarWindow.get_size();
    
        self.calendarWindow.move((x + rect.x - cal_width + rect.width), (y + rect.y + rect.height));
        self.calendarWindow.show();
    
        self.calendarWindow.move((x + rect.x - cal_width + rect.width), (y + rect.y + rect.height));
        
    def setSensitive(self,sensitive):
        PropertyEditor.setSensitive(self, sensitive);
        if self.calendarButton != None:
            self.calendarButton.set_sensitive(sensitive);
            
    def on_btnCancelCalendar_clicked(self,widget):
        self.calendarWindow.hide();
    
    def on_calendar_day_selected(self,widget):
        self.calendarWindow.hide();
    
    def on_btnClearCalendar_clicked(self,widget):
        self.calendarWindow.hide();
        
    def on_calendarPopup_focus_out_event(self,widget):
        self.calendarWindow.hide();
    
class LangAltPropertyEditor(PropertyEditor):
    
    def __init__(self,xmpType,lang,label):
        PropertyEditor.__init__(self, xmpType,lang, label);
        
    def setValues(self,value,values):
        self.oldValue = value;
        self.__setValue__(value);
        self.setDirty(False);
        multi = False;
        for v in values:
            if v != value:
                multi = True;
                break;
        self.setMulti(multi);
        
    def __setValue__(self,value):
        self.changingValue = True;
        try:
            if value == None or len(value) == 0:
                self.actualLang = self.lang;
                localizedText = None;
            else:
                localizedText = value.get(self.lang);
                self.actualLang = self.lang;
                if localizedText == None:
                    localizedText = value.get(xmp.DEFAULT_LANG);
                    self.actualLang = xmp.DEFAULT_LANG;
                if localizedText == None:
                    (self.actualLang,localizedText) = value.iteritems().next();
            if localizedText == None:
                localizedText = '';
            self.editWidget.set_text(localizedText);
        finally:
            self.changingValue = False;
        
    def getValue(self):
        dict = self.oldValue;
        text = self.editWidget.get_text();
        if len(text) == 0:
            del dict[self.actualLang];
        else:
            dict[self.actualLang] = text;
        return dict;
        
class ArrayElementPropertyEditor(PropertyEditor):
    
    def __init__(self,xmpType,lang,index):
        self.index = index;
        self.propertyEditor = createPropertyEditor(xmpType,lang,None);
        PropertyEditor.__init__(self, xmpType, lang, None);
        self.removed = False;
        self.added = False;
        self.propertyEditor.connect('dirty', self.onPropertyEditorDirty);

    def createEditWidget(self):
        self.editBox = self.box = gtk.HBox();
        self.box.pack_start(self.propertyEditor.editWidget);
        self.editWidget = self.propertyEditor.editWidget;
        self.editWidget.connect("key-press-event",self.onKeyPress);
        
        self.removeButton = gtk.Button('D');
#        image = gtk.Image();
#        image.set_from_icon_name('remove', 1);
#        self.removeButton.add(image);
#        self.removeButton.set_relief(gtk.RELIEF_NONE);
        self.removeButton.unset_flags(gtk.CAN_FOCUS);
        self.removeButton.connect('clicked',self.onButtonClicked,'remove');
        self.box.pack_end(self.removeButton,False,False);
        
    def onKeyPress(self,widget,event):
        if event.state & gtk.gdk.CONTROL_MASK:
            if gtk.gdk.keyval_name(event.keyval) == "minus":
                self.emit('remove');
                return True
        return False
        
    def onButtonClicked(self,button,event):
        self.emit(event);
        
    def createDirtyWidget(self):
        self.dirtyWidget = self.propertyEditor.dirtyWidget;
        
    def remove(self):
        grid = self.box.get_parent();
        if grid != None:
            grid.remove(self.box);
            grid.remove(self.dirtyWidget);
            if self.labelWidget != None:
                grid.remove(self.labelWidget);

    def setSensitive(self,sensitive):
        self.propertyEditor.setSensitive(sensitive);
        self.removeButton.set_sensitive(sensitive);
        if self.labelWidget != None:
            self.labelWidget.set_sensitive(sensitive);

    def setValue(self,value):
        self.propertyEditor.setValue(value);
    
    def setCatalog(self,catalog):
        self.propertyEditor.setCatalog(catalog);
        
    def configure(self,grid,position,last):
        position = PropertyEditor.configure(self, grid, position);
        return position;
    
    def getValue(self):
        return self.propertyEditor.getValue();
    
    def focus(self):
        self.propertyEditor.focus();
        
    def isDirty(self):
        return self.propertyEditor.isDirty();
    
    def setDirty(self,dirty):
        PropertyEditor.setDirty(self, dirty);
        self.propertyEditor.setDirty(dirty);

    def updateCatalog(self,catalog,file,property,path):
        catalog.setTagByValue(file,property,path,self.propertyEditor.getValue(),self.propertyEditor.oldValue);
        
    def setRemoved(self,removed):
        self.removed = removed;
        self.propertyEditor.setRemoved(removed);
        self.setDirty(removed);
        self.removeButton.set_sensitive(not removed);
        
    def isRemoved(self):
        return self.removed;
    
    def onPropertyEditorDirty(self,propertyEditor,dirty):
        self.dirty = dirty;
        if not dirty:
            if self.isRemoved():
                self.setRemoved(False);
    
    def isAdded(self):
        return self.added;
    
    def setAdded(self,added):
        self.added = added;
    
    def isEmpty(self):
        return self.propertyEditor.isEmpty();
    
    def setMulti(self,multi):
        self.propertyEditor.setMulti(multi);
    
class ArrayPropertyEditor(PropertyEditor):
    
    def __init__(self,xmpType,lang,label):
        PropertyEditor.__init__(self, xmpType, lang,label);
        self.elementsType = xmpType.elementsType;
        self.catalog = None;
        self.propertyEditors = [ self.createPropertyEditor(0,label) ];
        self.emptyPropertyEditor = createPropertyEditor(self.elementsType,self.lang,None);
        self.emptyPropertyEditor.connect('accept', self.onAdd);
        
    def createPropertyEditor(self,index,label = None):
        propertyEditor = ArrayElementPropertyEditor(self.elementsType,self.lang,index);
        propertyEditor.setCatalog(self.catalog);
        propertyEditor.connect('remove', self.onRemove);
        propertyEditor.propertyEditor.connect('dirty',self.onPropertyEditorDirty);
        return propertyEditor;
        
    def onPropertyEditorDirty(self,propertyEditor,dirty):
        if dirty:
            self.emit('dirty',True);
        else:
            for propertyEditor in self.propertyEditors:
                if propertyEditor.isDirty():
                    self.emit('dirty',True);
                    return;
            self.emit('dirty',False);
            
    def isDirty(self):
        for propertyEditor in self.propertyEditors:
            if propertyEditor.isDirty():
                return True;
        return False;
        
    def createEditWidget(self):
        self.editWidget = None;
    
    def configure(self,grid,position):
        self.attachWidget(grid,self.labelWidget,1,position,gtk.FILL,gtk.FILL);
        i = len(self.propertyEditors);
        for propertyEditor in self.propertyEditors:
            position = propertyEditor.configure(grid,position,i==1);
            i = i-1;
        self.emptyPropertyEditor.configure(grid, position);
        return position+1;
    
    def setSensitive(self,sensitive):
        PropertyEditor.setSensitive(self, sensitive);
        for propertyEditor in self.propertyEditors:
            propertyEditor.setSensitive(sensitive);
        self.emptyPropertyEditor.setSensitive(sensitive);
    
    def setValues(self,value,values):
        if value == None:
            value = ( );
        i = 0;
        for v in value:
            if i >= len(self.propertyEditors):
                self.propertyEditors.append(self.createPropertyEditor(i));
            self.propertyEditors[i].setValue(v);
            multi = False;
            for v2 in values:
                if v not in v2:
                    multi = True;
                    break;
            self.propertyEditors[i].setMulti(multi);
            self.propertyEditors[i].setRemoved(False);
            self.propertyEditors[i].setAdded(False);
            i = i+1;
#        self.propertyEditors.append(self.createPropertyEditor(len(value)));
        for i in range(len(value),len(self.propertyEditors)):
            self.propertyEditors[i].remove();
#       for propertyEditor in self.propertyEditors[len(value)+1:]:
#            propertyEditor.remove();
        self.propertyEditors = self.propertyEditors[:len(value)];
        self.emptyPropertyEditor.setValue(None);
        
    def setValue(self,value):
        self.setValues(value,());
        
    def setCatalog(self,catalog):
        self.catalog = catalog;
        for propertyEditor in self.propertyEditors:
            propertyEditor.setCatalog(catalog);
        self.emptyPropertyEditor.setCatalog(catalog);
            
    def getValue(self):
        coll = [];
        for propertyEditor in self.propertyEditors:
            coll.append(propertyEditor.getValue());
        return coll;
    
    def onAdd(self,propertyEditor):
        if not propertyEditor.isEmpty():
            newPropertyEditor = self.createPropertyEditor(len(self.propertyEditors));
            self.propertyEditors.append(newPropertyEditor);
            newPropertyEditor.focus();
            newPropertyEditor.setDirty(True);
            newPropertyEditor.setAdded(True);
            newPropertyEditor.setValue(propertyEditor.getValue());
            newPropertyEditor.setDirty(True);
            propertyEditor.setValue(None);
            self.emit('reconfigure');
        
    def onRemove(self,propertyEditor):
        if propertyEditor.isAdded():
            del self.propertyEditors[propertyEditor.index];
            propertyEditor.remove();
            index = propertyEditor.index;
            for propertyEditor in self.propertyEditors:
                if propertyEditor.index >= index:
                    propertyEditor.index = propertyEditor.index-1;
            self.emit('reconfigure');
        else:
            propertyEditor.setRemoved(True);

    def updateCatalog(self,catalog,file,property,path):
        for propertyEditor in propertyEditors:
            propertyEditor.updateCatalog(catalog,file,property,path);
